//
// request_parser.hpp
// ~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2012 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//

#ifndef HTTP_SERVER2_REQUEST_PARSER_HPP
#define HTTP_SERVER2_REQUEST_PARSER_HPP

extern "C" {
    #include "hiredis/sds.h"
}

#include "request.hpp"
#define MAX_REQUEST_SIZE 10240 // 10KB

namespace http {
namespace server3 {

struct request;

/// Parser for incoming requests.
class request_parser
{
public:
  /// Construct ready to parse the request method.
  request_parser();
  ~request_parser();
  enum parse_state {
      parse_completed = 0,
      parse_not_completed = 1,
      parse_error = 4
  };

  /// Reset to initial parser state.
  void reset();

  /// Parse some data.

  parse_state parse(request& req,
      char* begin, char* end)
  {
    parse_state result;
    if(end>begin+MAX_REQUEST_SIZE) return parse_error;
    while (begin != end)
    {
      result = consume(req, *begin++);
      if (result != parse_not_completed)
        return result;
    }
    return  parse_not_completed;
  }

private:

  char* buf;
  char* ptr;
  /// Handle the next character of input.
  inline parse_state consume(request& req, char input) {
      switch (state_)
    {
    case method_start:
      if (!is_char(input) || iscntrl(input) || is_tspecial(input))
      {
        return parse_error;
      }
      else
      {
        state_ = method;
        *ptr++ = input;
        //req.method.push_back(input);
        return parse_not_completed;
      }
    case method:
      if (input == ' ')
      {
        *ptr++='\0';
        req.method = buf;
        ptr=buf;
        state_ = uri;
        return parse_not_completed;
      }
      else if (!is_char(input) || iscntrl(input) || is_tspecial(input))
      {
        return parse_error;
      }
      else
      {
        *ptr++ = input;
        //req.method.push_back(input);
        return parse_not_completed;
      }
    case uri:
      if (input == ' ')
      {
        *ptr++='\0';
        req.uri = buf;
        ptr=buf;
        state_ = http_version_h;                
        return parse_not_completed;
      }
      else if (iscntrl(input))
      {
        return parse_error;
      }
      else
      {
        *ptr++=input;
        //req.uri.push_back(input);
        return parse_not_completed;
      }
    case http_version_h:
      if (input == 'H')
      {
        state_ = http_version_t_1;
        return parse_not_completed;
      }
      else
      {
        return parse_error;
      }
    case http_version_t_1:
      if (input == 'T')
      {
        state_ = http_version_t_2;
        return parse_not_completed;
      }
      else
      {
        return parse_error;
      }
    case http_version_t_2:
      if (input == 'T')
      {
        state_ = http_version_p;
        return parse_not_completed;
      }
      else
      {
        return parse_error;
      }
    case http_version_p:
      if (input == 'P')
      {
        state_ = http_version_slash;
        return parse_not_completed;
      }
      else
      {
        return parse_error;
      }
    case http_version_slash:
      if (input == '/')
      {
        req.http_version_major = 0;
        req.http_version_minor = 0;
        state_ = http_version_major_start;
        return parse_not_completed;
      }
      else
      {
        return parse_error;
      }
    case http_version_major_start:
      if (isdigit(input))
      {
        req.http_version_major = req.http_version_major * 10 + input - '0';
        state_ = http_version_major;
        return parse_not_completed;
      }
      else
      {
        return parse_error;
      }
    case http_version_major:
      if (input == '.')
      {
        state_ = http_version_minor_start;
        return parse_not_completed;
      }
      else if (isdigit(input))
      {
        req.http_version_major = req.http_version_major * 10 + input - '0';
        return parse_not_completed;
      }
      else
      {
        return parse_error;
      }
    case http_version_minor_start:
      if (isdigit(input))
      {
        req.http_version_minor = req.http_version_minor * 10 + input - '0';
        state_ = http_version_minor;
        return parse_not_completed;
      }
      else
      {
        return parse_error;
      }
    case http_version_minor:
      if (input == '\r')
      {
        state_ = expecting_newline_1;
        return parse_not_completed;
      }
      else if (isdigit(input))
      {
        req.http_version_minor = req.http_version_minor * 10 + input - '0';
        return parse_not_completed;
      }
      else
      {
        return parse_error;
      }
    case expecting_newline_1:
      if (input == '\n')
      {
        state_ = header_line_start;
        return parse_not_completed;
      }
      else
      {
        return parse_error;
      }
    case header_line_start:
      if (input == '\r')
      {
        state_ = expecting_newline_3;
        return parse_not_completed;
      }
      else if (!req.headers.empty() && (input == ' ' || input == '\t'))
      {
        *ptr++='\0';
        req.headers.back().name = buf;
        ptr=buf;
        state_ = header_lws;
        return parse_not_completed;
      }
      else if (!is_char(input) || iscntrl(input) || is_tspecial(input))
      {
        return parse_error;
      }
      else
      {
        // create a new header
        req.headers.push_back(header());
        *ptr++=input;
        //req.headers.back().name.push_back(input);
        state_ = header_name;
        return parse_not_completed;
      }
    case header_lws:
      if (input == '\r')
      {
        *ptr++='\0';
        req.headers.back().value = buf;
        ptr=buf;
        state_ = expecting_newline_2;
        return parse_not_completed;
      }
      else if (input == ' ' || input == '\t')
      {
        return parse_not_completed;
      }
      else if (iscntrl(input))
      {
        return parse_error;
      }
      else
      {
        state_ = header_value;
        *ptr++=input;
        //req.headers.back().value.push_back(input);
        return parse_not_completed;
      }
    case header_name:
      if (input == ':') // end of header_name
      {
        *ptr++='\0';
        req.headers.back().name = buf;
        ptr=buf;
        state_ = space_before_header_value;        
        return parse_not_completed;
      }
      else if (!is_char(input) || iscntrl(input) || is_tspecial(input))
      {
        return parse_error;
      }
      else
      {
        *ptr++=input;
        //req.headers.back().name.push_back(input);
        return parse_not_completed;
      }
    case space_before_header_value:
      if (input == ' ')
      {
        state_ = header_value;
        return parse_not_completed;
      }
      else
      {
        return parse_error;
      }
    case header_value:
      if (input == '\r')
      {
        *ptr++='\0';
        req.headers.back().value = buf;
        ptr=buf;
        state_ = expecting_newline_2;
        return parse_not_completed;
      }
      else if (iscntrl(input))
      {
        return parse_error;
      }
      else
      {
          *ptr++=input;
        //req.headers.back().value.push_back(input);
        return parse_not_completed;
      }
    case expecting_newline_2:
      if (input == '\n')
      {
        state_ = header_line_start;
        return parse_not_completed;
      }
      else
      {
        return parse_error;
      }
    case expecting_newline_3:
      if (input == '\n') return parse_completed;
      else return parse_error;
    default:
      return parse_error;
    }
  }

  /// Check if a byte is an HTTP character.
  static bool is_char(char c);


  /// Check if a byte is defined as an HTTP tspecial character.
  static bool is_tspecial(char c);

  /// The current state of the parser.
  enum state
  {
    method_start,
    method,
    uri,
    http_version_h,
    http_version_t_1,
    http_version_t_2,
    http_version_p,
    http_version_slash,
    http_version_major_start,
    http_version_major,
    http_version_minor_start,
    http_version_minor,
    expecting_newline_1,
    header_line_start,
    header_lws,
    header_name,
    space_before_header_value,
    header_value,
    expecting_newline_2,
    expecting_newline_3
  } state_;
};

} // namespace server2
} // namespace http

#endif // HTTP_SERVER2_REQUEST_PARSER_HPP
